/* raster.c - main and other misc stuff
 *
 * Raster graphics library
 *
 * Copyright (c) 1997-2003 Alfredo K. Kojima
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <X11/Xlib.h>
#include <assert.h>

#include "wraster.h"

char *WRasterLibVersion = "0.9";

int RErrorCode = RERR_NONE;

#define HAS_ALPHA(I) ((I)->format == RRGBAFormat)

#define MAX_WIDTH 20000
#define MAX_HEIGHT 20000
/* 20000^2*4 < 2G */

RImage *RCreateImage(unsigned width, unsigned height, int alpha)
{
  RImage *image = NULL;

  assert(width > 0 && height > 0);

  if (width > MAX_WIDTH || height > MAX_HEIGHT) {
    RErrorCode = RERR_NOMEMORY;
    return NULL;
  }

  image = malloc(sizeof(RImage));
  if (!image) {
    RErrorCode = RERR_NOMEMORY;
    return NULL;
  }

  memset(image, 0, sizeof(RImage));
  image->width = width;
  image->height = height;
  image->format = alpha ? RRGBAFormat : RRGBFormat;
  image->refCount = 1;

  /* the +4 is to give extra bytes at the end of the buffer,
   * so that we can optimize image conversion for MMX(tm).. see convert.c
   */
  image->data = malloc(width * height * (alpha ? 4 : 3) + 4);
  if (!image->data) {
    RErrorCode = RERR_NOMEMORY;
    free(image);
    image = NULL;
  }

  return image;
}

RImage *RRetainImage(RImage *image)
{
  if (image)
    image->refCount++;

  return image;
}

void RReleaseImage(RImage *image)
{
  assert(image != NULL);

  image->refCount--;

  if (image->refCount < 1) {
    free(image->data);
    free(image);
  }
}

RImage *RCloneImage(RImage *image)
{
  RImage *new_image;

  assert(image != NULL);

  new_image = RCreateImage(image->width, image->height, HAS_ALPHA(image));
  if (!new_image)
    return NULL;

  new_image->background = image->background;
  memcpy(new_image->data, image->data, image->width * image->height * (HAS_ALPHA(image) ? 4 : 3));

  return new_image;
}

RImage *RGetSubImage(RImage *image, int x, int y, unsigned width, unsigned height)
{
  int i, ofs;
  RImage *new_image;
  unsigned total_line_size, line_size;

  assert(image != NULL);
  assert(x >= 0 && y >= 0);
  assert(x < image->width && y < image->height);
  assert(width > 0 && height > 0);

  if (x + width > image->width)
    width = image->width - x;
  if (y + height > image->height)
    height = image->height - y;

  new_image = RCreateImage(width, height, HAS_ALPHA(image));

  if (!new_image)
    return NULL;
  new_image->background = image->background;

  total_line_size = image->width * (HAS_ALPHA(image) ? 4 : 3);
  line_size = width * (HAS_ALPHA(image) ? 4 : 3);

  ofs = x * (HAS_ALPHA(image) ? 4 : 3) + y * total_line_size;
  ;

  for (i = 0; i < height; i++) {
    memcpy(&new_image->data[i * line_size], &image->data[i * total_line_size + ofs], line_size);
  }
  return new_image;
}

/*
 *----------------------------------------------------------------------
 * RCombineImages-
 * 	Combines two equal sized images with alpha image. The second
 * image will be placed on top of the first one.
 *----------------------------------------------------------------------
 */
void RCombineImages(RImage *image, RImage *src)
{
  assert(image->width == src->width);
  assert(image->height == src->height);

  if (!HAS_ALPHA(src)) {
    if (!HAS_ALPHA(image)) {
      memcpy(image->data, src->data, image->height * image->width * 3);
    } else {
      int x, y;
      unsigned char *d, *s;

      d = image->data;
      s = src->data;
      for (y = 0; y < image->height; y++) {
        for (x = 0; x < image->width; x++) {
          *d++ = *s++;
          *d++ = *s++;
          *d++ = *s++;
          *d++ = 255;
        }
      }
    }
  } else {
    register int i;
    unsigned char *d;
    unsigned char *s;
    int alpha, calpha;

    d = image->data;
    s = src->data;

    if (!HAS_ALPHA(image)) {
      for (i = 0; i < image->height * image->width; i++) {
        alpha = *(s + 3);
        calpha = 255 - alpha;
        *d = (((int)*d * calpha) + ((int)*s * alpha)) / 256;
        d++;
        s++;
        *d = (((int)*d * calpha) + ((int)*s * alpha)) / 256;
        d++;
        s++;
        *d = (((int)*d * calpha) + ((int)*s * alpha)) / 256;
        d++;
        s++;
        s++;
      }
    } else {
      RCombineAlpha(d, s, 1, image->width, image->height, 0, 0, 255);
    }
  }
}

void RCombineImagesWithOpaqueness(RImage *image, RImage *src, int opaqueness)
{
  register int i;
  unsigned char *d;
  unsigned char *s;
  int c_opaqueness;

  assert(image->width == src->width);
  assert(image->height == src->height);

  d = image->data;
  s = src->data;

  c_opaqueness = 255 - opaqueness;

#define OP opaqueness
#define COP c_opaqueness

  if (!HAS_ALPHA(src)) {
    if (!HAS_ALPHA(image)) {
      for (i = 0; i < image->width * image->height; i++) {
        *d = (((int)*d * (int)COP) + ((int)*s * (int)OP)) / 256;
        d++;
        s++;
        *d = (((int)*d * (int)COP) + ((int)*s * (int)OP)) / 256;
        d++;
        s++;
        *d = (((int)*d * (int)COP) + ((int)*s * (int)OP)) / 256;
        d++;
        s++;
      }
    } else {
      RCombineAlpha(d, s, 0, image->width, image->height, 0, 0, OP);
    }
  } else {
    int tmp;

    if (!HAS_ALPHA(image)) {
      for (i = 0; i < image->width * image->height; i++) {
        tmp = (*(s + 3) * opaqueness) / 256;
        *d = (((int)*d * (255 - tmp)) + ((int)*s * tmp)) / 256;
        d++;
        s++;
        *d = (((int)*d * (255 - tmp)) + ((int)*s * tmp)) / 256;
        d++;
        s++;
        *d = (((int)*d * (255 - tmp)) + ((int)*s * tmp)) / 256;
        d++;
        s++;
        s++;
      }
    } else {
      RCombineAlpha(d, s, 1, image->width, image->height, 0, 0, opaqueness);
    }
  }
#undef OP
#undef COP
}

static int calculateCombineArea(RImage *des, int *sx, int *sy, unsigned int *swidth,
                                unsigned int *sheight, int *dx, int *dy)
{
  int width = (int)*swidth, height = (int)*sheight;

  if (*dx < 0) {
    *sx = -*dx;
    width = width + *dx;
    *dx = 0;
  }

  if (*dx + width > des->width) {
    width = des->width - *dx;
  }

  if (*dy < 0) {
    *sy = -*dy;
    height = height + *dy;
    *dy = 0;
  }

  if (*dy + height > des->height) {
    height = des->height - *dy;
  }

  if (height > 0 && width > 0) {
    *swidth = width;
    *sheight = height;
    return True;
  }

  return False;
}

void RCombineArea(RImage *image, RImage *src, int sx, int sy, unsigned width, unsigned height,
                  int dx, int dy)
{
  int x, y, dwi, swi;
  unsigned char *d;
  unsigned char *s;
  int alpha, calpha;

  if (!calculateCombineArea(image, &sx, &sy, &width, &height, &dx, &dy))
    return;

  if (!HAS_ALPHA(src)) {
    if (!HAS_ALPHA(image)) {
      swi = src->width * 3;
      dwi = image->width * 3;

      s = src->data + (sy * (int)src->width + sx) * 3;
      d = image->data + (dy * (int)image->width + dx) * 3;

      for (y = 0; y < height; y++) {
        memcpy(d, s, width * 3);
        d += dwi;
        s += swi;
      }
    } else {
      swi = (src->width - width) * 3;
      dwi = (image->width - width) * 4;

      s = src->data + (sy * (int)src->width + sx) * 3;
      d = image->data + (dy * (int)image->width + dx) * 4;

      for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
          *d++ = *s++;
          *d++ = *s++;
          *d++ = *s++;
          *d++ = 255;
        }
        d += dwi;
        s += swi;
      }
    }
  } else {
    int dalpha = HAS_ALPHA(image);

    swi = (src->width - width) * 4;
    s = src->data + (sy * (int)src->width + sx) * 4;
    if (dalpha) {
      dwi = (image->width - width) * 4;
      d = image->data + (dy * (int)image->width + dx) * 4;
    } else {
      dwi = (image->width - width) * 3;
      d = image->data + (dy * (int)image->width + dx) * 3;
    }

    if (!dalpha) {
      for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
          alpha = *(s + 3);
          calpha = 255 - alpha;
          *d = (((int)*d * calpha) + ((int)*s * alpha)) / 256;
          s++;
          d++;
          *d = (((int)*d * calpha) + ((int)*s * alpha)) / 256;
          s++;
          d++;
          *d = (((int)*d * calpha) + ((int)*s * alpha)) / 256;
          s++;
          d++;
          s++;
        }
        d += dwi;
        s += swi;
      }
    } else {
      RCombineAlpha(d, s, 1, width, height, dwi, swi, 255);
    }
  }
}

void RCopyArea(RImage *image, RImage *src, int sx, int sy, unsigned width, unsigned height, int dx,
               int dy)
{
  int x, y, dwi, swi;
  unsigned char *d;
  unsigned char *s;

  if (!calculateCombineArea(image, &sx, &sy, &width, &height, &dx, &dy))
    return;

  if (!HAS_ALPHA(src)) {
    if (!HAS_ALPHA(image)) {
      swi = src->width * 3;
      dwi = image->width * 3;

      s = src->data + (sy * (int)src->width + sx) * 3;
      d = image->data + (dy * (int)image->width + dx) * 3;

      for (y = 0; y < height; y++) {
        memcpy(d, s, width * 3);
        d += dwi;
        s += swi;
      }
    } else {
      swi = (src->width - width) * 3;
      dwi = (image->width - width) * 4;

      s = src->data + (sy * (int)src->width + sx) * 3;
      d = image->data + (dy * (int)image->width + dx) * 4;

      for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
          *d++ = *s++;
          *d++ = *s++;
          *d++ = *s++;
          d++;
        }
        d += dwi;
        s += swi;
      }
    }
  } else {
    int dalpha = HAS_ALPHA(image);

    swi = src->width * 4;
    s = src->data + (sy * (int)src->width + sx) * 4;
    if (dalpha) {
      dwi = image->width * 4;
      d = image->data + (dy * (int)image->width + dx) * 4;
    } else {
      dwi = image->width * 3;
      d = image->data + (dy * (int)image->width + dx) * 3;
    }

    if (dalpha) {
      for (y = 0; y < height; y++) {
        memcpy(d, s, width * 4);
        d += dwi;
        s += swi;
      }
    } else {
      for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
          *d++ = *s++;
          *d++ = *s++;
          *d++ = *s++;
          s++;
        }
        d += dwi;
        s += swi;
      }
    }
  }
}

void RCombineAreaWithOpaqueness(RImage *image, RImage *src, int sx, int sy, unsigned width,
                                unsigned height, int dx, int dy, int opaqueness)
{
  int x, y, dwi, swi;
  int c_opaqueness;
  unsigned char *s, *d;
  int dalpha = HAS_ALPHA(image);
  int dch = (dalpha ? 4 : 3);

  if (!calculateCombineArea(image, &sx, &sy, &width, &height, &dx, &dy))
    return;

  d = image->data + (dy * image->width + dx) * dch;
  dwi = (image->width - width) * dch;

  c_opaqueness = 255 - opaqueness;

#define OP opaqueness
#define COP c_opaqueness

  if (!HAS_ALPHA(src)) {
    s = src->data + (sy * src->width + sx) * 3;
    swi = (src->width - width) * 3;

    if (!dalpha) {
      for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
          *d = (((int)*d * (int)COP) + ((int)*s * (int)OP)) / 256;
          s++;
          d++;
          *d = (((int)*d * (int)COP) + ((int)*s * (int)OP)) / 256;
          s++;
          d++;
          *d = (((int)*d * (int)COP) + ((int)*s * (int)OP)) / 256;
          s++;
          d++;
        }
        d += dwi;
        s += swi;
      }
    } else {
      RCombineAlpha(d, s, 0, width, height, dwi, swi, OP);
    }
  } else {
    int tmp;

    s = src->data + (sy * src->width + sx) * 4;
    swi = (src->width - width) * 4;

    if (!dalpha) {
      for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
          tmp = (*(s + 3) * opaqueness) / 256;
          *d = (((int)*d * (255 - tmp)) + ((int)*s * tmp)) / 256;
          d++;
          s++;
          *d = (((int)*d * (255 - tmp)) + ((int)*s * tmp)) / 256;
          d++;
          s++;
          *d = (((int)*d * (255 - tmp)) + ((int)*s * tmp)) / 256;
          d++;
          s++;
          s++;
        }
        d += dwi;
        s += swi;
      }
    } else {
      RCombineAlpha(d, s, 1, width, height, dwi, swi, OP);
    }
  }
#undef OP
#undef COP
}

void RCombineImageWithColor(RImage *image, const RColor *color)
{
  register int i;
  unsigned char *d;
  int alpha, nalpha, r, g, b;

  d = image->data;

  if (!HAS_ALPHA(image)) {
    /* Image has no alpha channel, so we consider it to be all 255.
     * Thus there are no transparent parts to be filled. */
    return;
  }
  r = color->red;
  g = color->green;
  b = color->blue;

  for (i = 0; i < image->width * image->height; i++) {
    alpha = *(d + 3);
    nalpha = 255 - alpha;

    *d = (((int)*d * alpha) + (r * nalpha)) / 256;
    d++;
    *d = (((int)*d * alpha) + (g * nalpha)) / 256;
    d++;
    *d = (((int)*d * alpha) + (b * nalpha)) / 256;
    d++;
    d++;
  }
}

RImage *RMakeTiledImage(RImage *tile, unsigned width, unsigned height)
{
  int x, y;
  unsigned w;
  unsigned long tile_size = tile->width * tile->height;
  unsigned long tx = 0;
  RImage *image;
  unsigned char *s, *d;

  if (width == tile->width && height == tile->height)
    image = RCloneImage(tile);
  else if (width <= tile->width && height <= tile->height)
    image = RGetSubImage(tile, 0, 0, width, height);
  else {
    int has_alpha = HAS_ALPHA(tile);

    image = RCreateImage(width, height, has_alpha);

    d = image->data;
    s = tile->data;

    for (y = 0; y < height; y++) {
      for (x = 0; x < width; x += tile->width) {
        w = (width - x < tile->width) ? width - x : tile->width;

        if (has_alpha) {
          w *= 4;
          memcpy(d, s + tx * 4, w);
        } else {
          w *= 3;
          memcpy(d, s + tx * 3, w);
        }
        d += w;
      }

      tx = (tx + tile->width) % tile_size;
    }
  }
  return image;
}

RImage *RMakeCenteredImage(RImage *image, unsigned width, unsigned height, const RColor *color)
{
  int x, y, w, h, sx, sy;
  RImage *tmp;

  tmp = RCreateImage(width, height, HAS_ALPHA(image));
  if (!tmp) {
    return NULL;
  }

  RFillImage(tmp, color);

  if (image->height < height) {
    h = image->height;
    y = (height - h) / 2;
    sy = 0;
  } else {
    sy = (image->height - height) / 2;
    y = 0;
    h = height;
  }
  if (image->width < width) {
    w = image->width;
    x = (width - w) / 2;
    sx = 0;
  } else {
    sx = (image->width - width) / 2;
    x = 0;
    w = width;
  }
  RCombineArea(tmp, image, sx, sy, w, h, x, y);

  return tmp;
}
